﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Microsoft.Ccr.Core;
using NUnit.Framework;
using System.Diagnostics;
using Task = System.Threading.Tasks.Task;

namespace test.XcoAppSpaces.Core.Ports
{
	[TestFixture]
	public class PerformanceTests
	{
		[Test]
		public void TestProcessingPerformance_Impl_Multiple()
		{
			int count = 1000000;
			long result = MeasurePerformance("New Port Impl", () => PortPerformance(count));
			result += MeasurePerformance("New Port Impl", () => PortPerformance(count));
			result += MeasurePerformance("New Port Impl", () => PortPerformance(count));
			result /= 3;
			Console.WriteLine("Avg result : " + result + " ms");
		}

		[Test]
		public void TestProcessingPerformance_All()
		{
			int count = 1000000;
			MeasurePerformance("New Port Impl", () => PortPerformance(count));
			MeasurePerformance("TPL Tasks", () => TaskPerformance(count));
			MeasurePerformance("TPL Parallel For", () => ParallelLoopPerformance(count));
			MeasurePerformance("ActionBlock", () => ActionBlockPerformance(count));
			MeasurePerformance("CCR Port", () => CcrPortPerformance(count));
		}

		private long MeasurePerformance(string name, Action action)
		{
			Thread.Sleep(300);
			Stopwatch sw = Stopwatch.StartNew();
			action();
			sw.Stop();
			Console.WriteLine("Performance of " + name + ": " + sw.ElapsedMilliseconds + "ms");
			return sw.ElapsedMilliseconds;
		}

		private void PortPerformance(int count)
		{
			int current = 0;
			var port = new global::XcoAppSpaces.Core.Ports.Port<int>();
			port.RegisterReceiver(_ =>
				{
					DoSomething();
					Interlocked.Increment(ref current);
				}, 4);

			for (int i = 0; i < count; i++)
			{
				port.Post(i);
			}

			SpinWait.SpinUntil(() => current == count);
		}

		private void TaskPerformance(int count)
		{
			int current = 0;
			for (int i = 0; i < count; i++)
			{
				Task.Factory.StartNew(() =>
					{
						DoSomething();
						Interlocked.Increment(ref current);
					});
			}

			SpinWait.SpinUntil(() => current == count);
		}

		private void ParallelLoopPerformance(int count)
		{
			int current = 0;
			Parallel.For(0, count, _ =>
				{
					DoSomething();
					Interlocked.Increment(ref current);
				});
			

			SpinWait.SpinUntil(() => current == count);
		}

		private void ActionBlockPerformance(int count)
		{
			int current = 0;
			var block = new ActionBlock<int>(_ =>
			{
				DoSomething();
				Interlocked.Increment(ref current);
			}, new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism = 4});

			for (int i = 0; i < count; i++)
			{
				block.Post(i);
			}

			SpinWait.SpinUntil(() => current == count);
		}

		private void CcrPortPerformance(int count)
		{
			using (var dispatcher = new Dispatcher())
			using (var dpq = new DispatcherQueue("test", dispatcher))
			{
				int current = 0;
				var port = new Port<int>();
				Arbiter.Activate(dpq, Arbiter.Receive(true, port, 
					_ =>
					{
						DoSomething();
						Interlocked.Increment(ref current);
					}));

				for (int i = 0; i < count; i++)
				{
					port.Post(i);
				}

				SpinWait.SpinUntil(() => current == count);
			}
		}

		private void DoSomething()
		{
			Math.Sqrt(1234567.1234567);
		}
	}
}
